/*
 * Copyright (c) 2025 HiEasyX
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * \file hex.cpp
 * \brief The HiEasyX major code
 */

#include <include/hex.h>

#include <map>
#include <vector>

#undef max

namespace HX {
HXTheme          Theme;
HXRuntimeContext Context;
HXMessageSender *MsgSender;

HXRuntimeContext &GetContext() {
	return Context;
}

HXTheme &GetTheme() {
	return Theme;
}

void SetBuffer(void *Buffer) {
	Context.LocalBuffer = Buffer;
}

void OSAPI(HXOSOperation *API) {
	Context.OSAPI = API;
}

HXString GetLastError() {
	return Context.LastError;
}

void MessageSender(HXMessageSender *Sender) {
	MsgSender = Sender;
	if (Sender == nullptr) {
		Context.Win       = false;
		Context.LastError = HXStr("Invalid message sender");
	}
}

void PushMessage(void *Message) {
	Context.MessageQuery.push_back(MsgSender->Message(Message));
}

void Begin(HXContext *RenderContext) {
	HX::LockEasyXLock();

	for (auto &window : Context.Windows) {
		delete window;
	}

	Context.Windows.clear();

	if (Context.Initialized) {
		Context.Win       = false;
		Context.LastError = HXStr("End is needed for another UI layout progress");
	} else {
		Context               = HXRuntimeContext{};
		Context.RenderContext = RenderContext;
		Context.Initialized   = true;
	}
}

void WindowLocate(HXPoint Where) {
	Context.CurrentWindow->Where = Where;
}

void End() {
	HX::UnlockEasyXLock();

	Context.Initialized = false;
}

bool Wined() {
	return Context.Win;
}

void CreateTheme() {
	Theme = HXTheme{
		HXColor{180, 180, 180, 255},
	HXColor{21, 22, 23, 255},
	HXColor{255, 255, 255, 255},
	HXColor{41, 74, 122, 255},
	HXColor{39, 73, 114, 255},
	HXColor{39, 73, 114, 255},
	HXColor{255, 255, 255, 255},
	HXColor{49, 83, 124, 255},
	HXColor{49, 83, 124, 255},
	HXColor{255, 255, 255, 255},
	HXColor{59, 93, 134, 255},
	HXColor{59, 93, 134, 255},
	HXColor{255, 255, 255, 255},
	HXColor{255, 255, 255, 255},
	HXColor{32, 45, 79, 255},
	HXColor{255, 255, 255, 255},
	HXColor{42, 55, 89, 255},
	HXColor{61, 133, 224, 255},
	HXColor{32, 50, 77, 255},
	HXColor{66, 149, 249},
	HXColor{255, 255, 255, 255},
	HXColor{21, 22, 23, 255},
	HXColor{32, 50, 77, 255},
	HXColor{32, 50, 77, 255},
	HXColor{255, 255, 255, 255},
	HXColor{42, 60, 87, 255},
	HXColor{42, 60, 87, 255},
	HXColor{255, 255, 255, 255},
	HXColor{21, 22, 23, 255},
	HXColor{91, 92, 93, 255},
	HXColor{255, 255, 255, 255},
	HXColor{69, 86, 112, 255},
	HXColor{255, 255, 255, 255}
	};
}

void Render() {
	HXBufferPainter *Painter = Context.RenderContext->DefaultPainter()->CreateFromBuffer(Context.LocalBuffer);
	for (auto window = Context.Windows.rbegin(); window != Context.Windows.rend(); ++window) {
		Painter->DrawPainter((*window)->Painter, (*window)->Where);
	}
}

HXPoint ClipCoord(HXPoint Point) {
	return {Point.X - Context.CurrentWindow->Where.X - Context.CurrentWindow->ScrollerX,
	        Point.Y - Context.CurrentWindow->Where.Y - Context.CurrentWindow->ScrollerY};
}

void SetControlGap(HXGInt Gap) {
	Context.CurrentWindow->CurrentLayout->ControlGap = Gap;
}

void SetBaseLine(HXGInt BaseLine) {
	Context.CurrentWindow->CurrentLayout->BaseLine = BaseLine;
}

void LayoutNextControl(HXPoint Size) {
	auto layout = Context.CurrentWindow->CurrentLayout;

	layout->CurrentHeightMax = std::max(Size.Y, layout->CurrentHeightMax);

	if (!layout->InSameLine) {
		layout->BaseLine += layout->CurrentHeightMax + layout->ControlGap;
		layout->LeftPadding      = layout->LeftPaddingValue;
		layout->CurrentHeightMax = 0;
	} else {
		if (!layout->LockingSameLine) {
			layout->InSameLine = false;
		}
		layout->LeftPadding += Size.X + layout->ControlGap * 2;
	}
}

HXPoint CurrentLayoutPosition() {
	auto layout = Context.CurrentWindow->CurrentLayout;

	return {layout->LeftMargin + layout->LeftPadding, layout->BaseLine};
}

void BeginSameLine() {
	auto layout = Context.CurrentWindow->CurrentLayout;

	layout->InSameLine      = true;
	layout->LockingSameLine = true;
}

bool RoughVisibility() {
	if (Context.CurrentWindow->Scroller != nullptr && !Context.CurrentWindow->Scroller->Visible) {
		return true;
	}

	auto layout   = CurrentLayout();
	auto position = CurrentLayoutPosition();
	if (position.X > layout->AvailableRegion.Right || position.Y > layout->AvailableRegion.Bottom) {
		return true;
	}

	return Context.CurrentWindow->Folded;
}

bool AccVisibility(HXPoint Size) {
	if (!CurrentVisibility(Size)) {
		if (NeedLayoutCal()) {
			LayoutNextControl(Size);
		}

		return true;
	}

	return false;
}

bool CurrentVisibility(HXPoint Size) {
	auto position = CurrentLayoutPosition();
	auto layout   = CurrentLayout();

	if (Context.CurrentWindow->Folded) {
		return false;
	}
	if (position.X > layout->AvailableRegion.Right || position.Y > layout->AvailableRegion.Bottom) {
		return false;
	}

	HXRect ControlRect = {position.X, position.Y, position.X + Size.X, position.Y + Size.Y};

	return (ControlRect.Left < layout->AvailableRegion.Right) && (layout->AvailableRegion.Left < ControlRect.Right) &&
	       (ControlRect.Top < layout->AvailableRegion.Bottom) && (layout->AvailableRegion.Top < ControlRect.Bottom);
}

bool NeedLayoutCal() {
	auto position = CurrentLayoutPosition();

	if (Context.CurrentWindow->Folded) {
		return false;
	}

	if (position.X < CurrentLayout()->AvailableRegion.Left || position.Y < CurrentLayout()->AvailableRegion.Top) {
		return true;
	}

	return false;
}

HXBufferPainter *CurrentPainter() {
	if (Context.CurrentWindow->Scroller != nullptr) {
		return Context.CurrentWindow->Scroller->Painter;
	}

	return Context.CurrentWindow->Painter;
}

void EndSameLine() {
	auto layout = Context.CurrentWindow->CurrentLayout;

	layout->InSameLine      = false;
	layout->LockingSameLine = false;

	layout->BaseLine += layout->CurrentHeightMax + layout->ControlGap;
	layout->LeftPadding      = layout->LeftPaddingValue;
	layout->CurrentHeightMax = 0;
}

HXLayoutContext *CurrentLayout() {
	return Context.CurrentWindow->CurrentLayout;
}

void SetLeftMargin(HXGInt Margin) {
	CurrentLayout()->LeftMargin = Margin;
}

}
